package com.gxgeek.calculator;

import com.gxgeek.CoreConstants;
import com.gxgeek.calculator.script.Unit;
import com.gxgeek.calculator.transfomer.ScriptTransformer;
import com.gxgeek.entity.DimensionEntity;
import com.gxgeek.model.Cell;
import com.gxgeek.repository.DimensionRepository;
import groovy.lang.GroovyClassLoader;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static com.gxgeek.ScriptUtils.loadGroovyScript;

/**
 * @author gaoxing
 * @create 2018-06-04 下午7:54
 */
@Service
@Slf4j
public class CellContainer implements BeanFactoryAware,
        ApplicationListener<ContextRefreshedEvent>
        , ApplicationContextAware {
    private DefaultListableBeanFactory beanFactory;
    private ApplicationContext context;

    @Autowired
    private ScriptTransformer scriptTransformer;
    @Autowired
    private DimensionRepository dimensionRepository;
    private Map<String, Cell> cellMap = new ConcurrentHashMap<>(1 << 10);//1024

    public  <T> T getBean(Class<T> clazz) {
        T t = context.getBean(clazz);
        return t;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (DefaultListableBeanFactory) beanFactory;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        //todo we need reload script without by restarting service
        List<DimensionEntity> pos = dimensionRepository.findAll();
        pos.forEach(dimension -> {
            cellMap.put(dimension.getKey(), buildCell(dimension));
        });

    }



    private Cell buildCell(DimensionEntity dimension) {
        String key = dimension.getKey();
        String codeSegment = dimension.getScript();
        //todo 根据脚本类型选择合适的转换器翻译

        String beanName = CoreConstants.Data.SCRIPT_NAME_PREFIX + key;
        if (!context.containsBean(beanName)) {
            String script = null;
            try {
                script = scriptTransformer.transform(key, codeSegment);
                Class GroovyClass = loadGroovyScript(script, beanName);
                BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(GroovyClass);
                ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) context;
                BeanDefinitionRegistry beanDefinitonRegistry = (BeanDefinitionRegistry) configurableApplicationContext
                        .getBeanFactory();
                beanDefinitonRegistry.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
            } catch (Exception e) {
                log.error("计算单元 [{}] 加载失败,脚本信息:\n {}",key,script,e);
                throw new RuntimeException();
            }
        }
        Unit unit = context.getBean(beanName, Unit.class);

        return Cell.builder()
                .key(key)
                .cache(dimension.isCache())
                .expireTime(dimension.getPeriod())
                .type(dimension.getType())
                .unit(unit)
                .build();

    }

    public Cell getCell(String key) {
        return cellMap.get(key);
    }
}
